/*
 * @Author: xiaosihan
 * @Date: 2021-05-05 14:18:27
 * @Last Modified by: xiaosihan
 * @Last Modified time: 2021-11-07 19:17:58
 */

import _ from "lodash";
import * as THREE from "three";
import { Texture } from "three";
import { Points as THREEPoints } from "three/src/objects/Points.js";
import textureCache from "../cache/textureCache";
import Object3D from "../Object3D/Object3D";
import Object3DComponent, { Object3DComponentProps } from "../Object3D/Object3DComponent";
import { Object3DEvent } from "../Renderer/type";
import Transition from "../xrt_util/Transition";
import TransitionColor from "../xrt_util/TransitionColor";

export interface PointsProps extends Object3DComponentProps {
    points?: Array<[number, number, number]>, // 点位的数量一定要是3的倍数
    size?: number, // 点大小
    sizeAttenuation?: boolean, // 是否有近大远小的效果
    blending?: boolean, // 重叠部分是否有颜色叠加
    color?: string; // 叠加颜色
    map?: Texture | string;

    alphaMap?: Texture | string;// 透明通道纹理
    alphaTest?: number;// 透明通道纹理
    alphaToCoverage?: boolean; // 透明通道的范围

    depthTest?: boolean; // 深度测试
    depthWrite?: boolean; // 深度写

    opacity?: number;// 透明度
}

interface State { }

export default class Points extends Object3DComponent<PointsProps, State> {
    constructor(props: PointsProps) {
        super(props);

        // 创建基础对象object3d;
        let name = this.getConstructorName();

        this.object3d = new (Function("THREEPoints", `
            return class ${name} extends THREEPoints {};
        `)(THREEPoints));

    }

    declare object3d: THREEPoints & Object3D;

    // 点模型
    pointsGeometry = (() => {
        let pointsGeometry = new THREE.BufferGeometry();
        pointsGeometry.drawRange.count = 1;
        pointsGeometry.setAttribute("position", new THREE.Float32BufferAttribute([0, 0, 0], 3));
        return pointsGeometry;
    })();

    // 线条
    pointsMaterial = new THREE.PointsMaterial();

    // 带有缓动效果的线顶点对象
    TPointsVector3 = new Transition();

    Tcolor = new TransitionColor(this.props.color || "#fff");

    // 线条的缓动参数
    TPointsParam = new Transition();

    componentDidMount() {
        this.object3d.geometry.dispose();
        this.object3d.material.dispose();
        this.object3d.geometry = this.pointsGeometry;
        this.object3d.material = this.pointsMaterial;

        this.setPointsGeometry();

        this.setPointsMaterial();

        this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, this.setTransformVector3Position);
    }

    componentDidUpdate() {
        this.setPointsGeometry();

        this.setPointsMaterial();

        this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.setTransformVector3Position);
        this.object3d.addEventListener(Object3DEvent.BEFORE_RENDER, this.setTransformVector3Position);
    }

    componentWillUnmount() {
        const { map } = this.props;
        this.object3d.geometry.dispose();

        if (_.isString(map)) {
            this.object3d.material.dispose();
        }

        this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.setTransformVector3Position);
    }

    // 设置线模型
    setPointsGeometry() {

        const {
            bezier,
            duration = 500,
            points = []
        } = this.props;

        const { pointsGeometry } = this;

        let newPoints = _.flatten(points);// 把传入的参数转成顶点格式 一维数组

        this.TPointsVector3.setBezier(bezier).setDuration(duration).set(newPoints);

        // 长度不相等时 重新创建 线条position顶点对象
        if (pointsGeometry.attributes.position.array.length < newPoints.length) {

            let originPoints = this.TPointsVector3.getJson();

            for (let i in newPoints) {

                if (!_.isUndefined(originPoints[i])) {
                    newPoints[i] = originPoints[i];
                } else {
                    continue;
                }
            }

            pointsGeometry.attributes.position = new THREE.Float32BufferAttribute(newPoints, 3);
        }

        pointsGeometry.drawRange.count = points.length;
    }

    setPointsMaterial() {
        const {
            bezier,
            duration = 500,
            map,
            size = 4,
            sizeAttenuation = true, // 是否有近大远小的效果
            blending = true, // 重叠部分是否有颜色叠加

            color = "#fff", // 叠加颜色

            alphaMap,// 透明通道纹理
            alphaTest = 0.1,// 透明通道纹理
            alphaToCoverage = true, // 开启透明通道

            depthTest = true, // 深度测试
            depthWrite = true, // 深度写

            opacity = 1,// 透明度

            clipping = true//--------------------------------------开启裁剪
        } = this.props;

        this.Tcolor
            .setBezier(bezier)
            .setDuration(duration)
            .set(color);

        this.TPointsParam
            .setBezier(bezier)
            .setDuration(duration)
            .set({ size, alphaTest, opacity });


        Object.assign(this.pointsMaterial, {
            clipping,//--------------------------------------开启裁剪
            transparent: true,
            sizeAttenuation, // 是否有近大远小的效果
            blending: blending ? THREE.NormalBlending : THREE.AdditiveBlending, // 重叠部分是否有颜色叠加
            alphaToCoverage, // 透明通道的范围
            depthTest, // 深度测试
            depthWrite, // 深度写
        });

        // 设置纹理
        this.setMap("map", map);
        this.setMap("alphaMap", alphaMap);

        this.pointsMaterial.needsUpdate = true;
    }

    // 设置各种纹理的方式
    setMap(mapName: string, map?: THREE.Texture | string | undefined) {

        // 如果材质不支持这个纹理的话就不忘下执行了
        if (!this.object3d.material.hasOwnProperty(mapName)) {
            return;
        }

        const { textureLoader, renderer } = this.context;

        // // 设置最大各向异性 处理模糊问题
        let anisotropy = renderer.capabilities.getMaxAnisotropy();

        // 纹理置空
        if (_.isEmpty(map)) {

            (this.pointsMaterial as any)[mapName] = null;

            // 直接赋值
        } else if (_.get(map, ["isTexture"])) {

            (map as Texture).anisotropy = anisotropy;

            (this.pointsMaterial as any)[mapName] = map;

        } else if (_.isString(map)) {

            // 从缓存中去取
            let texture = textureCache.get(map);

            // 缓存中也没有就去加载
            if (!texture) {
                texture = textureLoader.load(map);
                textureCache.set(map, texture);
            }

            texture!.anisotropy = anisotropy;

            (this.pointsMaterial as any)[mapName] = texture;
        }
    }



    setTransformVector3Position = () => {
        const { pointsGeometry, pointsMaterial } = this;

        let keepRun = false;

        // 设置每个顶点的位置
        if (!this.TPointsVector3.isDone()) {

            for (let i = 0; i < pointsGeometry.drawRange.count; i++) {
                pointsGeometry.attributes.position.setXYZ(
                    i,
                    this.TPointsVector3.get(i * 3),
                    this.TPointsVector3.get((i * 3) + 1),
                    this.TPointsVector3.get((i * 3) + 2)
                );
            }

            pointsGeometry.attributes.position.needsUpdate = true; // 标记顶点位置需要更新

            keepRun = true;
        }

        // 设置材质上面的参数
        if (!this.TPointsParam.isDone()) {
            const { opacity, size, alphaTest } = this.TPointsParam.getJson();

            Object.assign(pointsMaterial, {
                opacity,
                size,
                alphaTest
            });

            keepRun = true;
        }

        // 颜色叠加
        if (!this.Tcolor.isDone()) {
            const { r, g, b } = this.Tcolor.getJson();
            pointsMaterial.color.setRGB(r, g, b);
            keepRun = true;
        }

        if (keepRun) {
            this.pointsMaterial.needsUpdate = true;
        }

        if (!keepRun) {
            this.object3d.removeEventListener(Object3DEvent.BEFORE_RENDER, this.setTransformVector3Position);
        }

    }

}